use actix_web::{web, HttpResponse,Result};
use prometheus_client::encoding::text::encode;
use prometheus_client::metrics::counter::Counter;
use prometheus_client::metrics::family::Family;
use prometheus_client::registry::Registry;
use serde::{Deserialize, Serialize};

//mod selfstat;
pub use crate::selfstat::*;

#[derive(Debug, Serialize, Deserialize)]
pub struct ProcObj {
    comm: String,
    state: String,
    pid: i32,
}

#[derive(Debug, Serialize, Deserialize)]
pub struct ProcName {
    name: String,
}

/// This handler uses json extractor
pub async fn index(item: web::Json<ProcObj>) -> HttpResponse {
    log::debug!("model: {:?}", &item);
    HttpResponse::Ok().json(item.0) // <- send response
}


pub async fn get_process_list(metrics: web::Data<OwnMetrics>) -> HttpResponse {
    metrics.inc_requests(Method::Get);
    let tps = procfs::ticks_per_second();
    let mut jresp: Vec<ProcObj> = Vec::new();
    for p in procfs::process::all_processes().unwrap() {
        let prc = p.unwrap();
        if let Ok(stat) = prc.stat() {
            // total_time is in seconds
            let total_time = (stat.utime + stat.stime) as f32 / (tps as f32);
            if total_time > 0.0 {
                jresp.push(ProcObj {
                    comm: stat.comm,
                    state: stat.state.to_string(),
                    pid: stat.pid,
                });
            }
            //println!("{: >10} {: >8} {}", stat.pid, total_time, stat.comm);
        }
    }
    HttpResponse::Ok().json(jresp) // <- send response
}

pub struct ProcMetrics {
    pub cpu_time: Family<CpuLabels, Counter>,
    pub ctx_switch: Family<CtxLabels, Counter>,
    pub io_bytes: Family<IOLabels, Counter>,
}


impl ProcMetrics {
    pub fn update_cpu(&self, comm_name: String, pid: i32, cpu_type: CpuTime, now_value: u64) {
        //let cpu_type_clone = cpu_t.clone();
        let cpu = self
            .cpu_time
            .get_or_create(&CpuLabels {
                comm_name,
                pid,
                cpu_type,
            })
            .clone();
        let v = cpu.get();
        let new_value = now_value - v;
        if new_value > 0 {
            cpu.inc_by(new_value);
        }
    }
    pub fn update_io(
        &self,
        comm_name: String,
        pid: i32,
        io_type: IOType,
        dev: String,
        dev_type: DevType,
    ) {
        self.io_bytes
            .get_or_create(&IOLabels {
                comm_name,
                pid,
                io_type,
                dev,
                dev_type,
            })
            .inc();
    }
    pub fn update_ctx(&self, comm_name: String, pid: i32, ctx_type: CtxType, now_value: u64) {
        let ctx = self
            .ctx_switch
            .get_or_create(&CtxLabels {
                comm_name,
                pid,
                ctx_type,
            })
            .clone();
        let v = ctx.get();

        let new_value = now_value - v;
        if new_value > 0 {
            ctx.inc_by(new_value);
        }
    }
}


pub async fn get_process_metrics(metrics: web::Data<OwnMetrics>, item: web::Json<ProcName>) -> Result<HttpResponse> {
    metrics.inc_requests(Method::Get);

    let process_metrics = web::Data::new(ProcMetrics {
        cpu_time: Family::default(),
        ctx_switch: Family::default(),
        io_bytes: Family::default(),
    });

    let mut state = AppState {
        registry: Registry::default(),
    };
    state.registry.sub_registry_with_prefix("rdaemon").register(
        "cpu_seconds_time",
        "Count of cpu time",
        process_metrics.cpu_time.clone(),
    );
    state.registry.sub_registry_with_prefix("rdaemon").register(
        "ctx_switch",
        "Count of ctx switch",
        process_metrics.ctx_switch.clone(),
    );
    state.registry.sub_registry_with_prefix("rdaemon").register(
        "io_bytes",
        "Count of io bytes",
        process_metrics.io_bytes.clone(),
    );
    let comm = item.0.name;

    for p in procfs::process::all_processes().unwrap() {
        let prc = p.unwrap();
        if let Ok(stat) = prc.stat() {
            if comm.as_str() == stat.comm.as_str() {
                    process_metrics.update_cpu(stat.comm, stat.pid, CpuTime::System, stat.stime);
                    let me_stat = prc.stat().unwrap();
                    process_metrics.update_cpu(me_stat.comm, me_stat.pid, CpuTime::User, me_stat.stime);
                }
            }
        }
    let mut body = String::new();
    encode(&mut body, &state.registry).unwrap();
    Ok(HttpResponse::Ok()
        .content_type("application/openmetrics-text; version=1.0.0; charset=utf-8")
        .body(body))
}